#include "iConfig.hpp"
#include <glf/task/taskGroups.h>
#include <glf/mem/ephemeralAllocator.h>

namespace glf {
namespace task_detail {


class Group
{
public:
	// Allocated using Pool allocator with bucket size of 8 to 64
	// Owned by the thread until it own more than the bucket can handle
	// release or grab half of the bucket size when needed
	Group(TaskGroupScope* scope)
		: m_Scope(scope)
		, m_Sibbling(0)
		, m_FirstChild(0)
		, m_Parent(0)
		, m_PendingTask(0)
		, m_ReferenceCounter(0)
	{}

	~Group()
	{
		GLF_ASSERT(m_ReferenceCounter == 0);
		Group* childIt = m_FirstChild;
		while(childIt)
		{
			Group* next = childIt->m_Sibbling;
			childIt->Drop();
			childIt = next;
		}
	}

	void Grab()
	{
		++m_ReferenceCounter;
	}

	void Drop()
	{
		if(--m_ReferenceCounter == 0)
		{
			delete this;
		}
	}

	void TaskPushed()
	{
		Grab();
		int pendingTaskCount = ++m_PendingTask;
		//printf("%d ", pendingTaskCount);
		if(pendingTaskCount == 1)
		{
			if(m_Parent)
			{
				m_Parent->TaskPushed();
			}
		}
	}

	void TaskCompleted()
	{
		int pendingTaskCount = --m_PendingTask;
		//printf("%d ", pendingTaskCount);
		if(pendingTaskCount == 0)
		{
			if(m_Parent)
			{
				m_Parent->TaskCompleted();
			}
		}
		Drop();
	}

	bool HasPendingTask()
	{
		return (m_PendingTask != 0);
	}

	void Add(Group* child)
	{
		child->m_Parent = this;
		child->m_Sibbling = m_FirstChild;
		m_FirstChild = child;
		m_FirstChild->Grab();
	}

	Group* Find(TaskGroupScope* scope)
	{
		Group* childIt = m_FirstChild;
		while(childIt)
		{
			if(childIt->m_Scope == scope)
			{
				return childIt;
			}
			childIt = childIt->m_Sibbling;
		}
		return 0;
	}

	Group* GetParent() const
	{
		return m_Parent;
	}

	TaskGroupScope* GetTaskGroupScope() const
	{
		return m_Scope;
	}

	void* operator new(size_t size)
	{
		EphemeralAllocator<char> allocator;
		return allocator.allocate(size);
	}

	void operator delete(void* ptr)
	{
		EphemeralAllocator<char> allocator;
		return allocator.deallocate((char*)ptr);
	}

protected:
	TaskGroupScope* m_Scope;
	Atomic32 m_ReferenceCounter;
	Atomic32 m_PendingTask;
	Group* m_Parent;
	Group* m_FirstChild;
	Group* m_Sibbling;
};

namespace
{	

GLF_DECLARE_TLS(Group*, g_ThisGroup, extern);
GLF_DEFINE_TLS(Group*, g_ThisGroup) = { NULL };



void PushGroup(Group* group)
{
	GLF_ASSERT(group);
	Group* currentGroup = GetGroup();
	if(currentGroup)
	{
		currentGroup->Add(group);
	}
	group->Grab();
	SetGroup(group);
}

void PopGroup()
{
	Group* old = GetGroup();
	g_ThisGroup.get() = GetGroup()->GetParent();
	if(old)
	{
		old->Drop();
	}
}

} // end of namespace anonymous

Group* GetGroup()
{
	return g_ThisGroup.get();
}

Group* SetGroup(Group* group)
{
	Group*& current = g_ThisGroup.get();
	Group* old = current;
	current = group;
	return old;
}

Group* GrabGroup()
{
	Group* group = GetGroup();
	if(group)
	{
		group->TaskPushed();
	}
	return group;
}

void DropGroup(Group* group)
{
	if(group)
	{
		group->TaskCompleted();
	}
}

} // end of namespace task_groups

TaskGroupScope::TaskGroupScope()
: m_Group(NULL)
#if GLF_USE_ASSERT
, m_PushPopCounter(0)
#endif
{
}

TaskGroupScope::~TaskGroupScope()
{
	GLF_ASSERT(m_PushPopCounter == 0);
	if(m_Group)
	{
		m_Group->Drop();
	}
}

void TaskGroupScope::Push()
{
	GLF_ASSERT_ONLY(++m_PushPopCounter);
	GLF_ASSERTMSG(!m_Group || m_Group == task_detail::GetGroup()->Find(this), "A task tag can be pushed more than one time as long as it's in the same context");
	/** Exemple
	TaskGroupScope B, C, D;
	B.Push();
	C.Push();
	C.Pop();
	D.Push();
	D.Pop();
	C.Push(); // It's fine. C is pushed back in the B context.
	D.Push(); // Not correct. D was previously pushed in the B context now trying in the C one.
	*/

	if(m_Group == NULL)
	{
		m_Group = new task_detail::Group(this);
		m_Group->Grab();
	}
	task_detail::PushGroup(m_Group);
}

void TaskGroupScope::Pop()
{
	GLF_ASSERT_ONLY(--m_PushPopCounter);
	GLF_ASSERTMSG(task_detail::GetGroup() == m_Group, "TaskGroupScope::Pop can't be called if this one is not the active one");
	task_detail::PopGroup();
}

task_detail::Group* TaskGroupScope::GetGroup()
{
	return m_Group;
}

bool TaskGroupScope::HasPendingTask()
{
	return HasPendingTask(m_Group);
}

bool TaskGroupScope::HasPendingTask(task_detail::Group* group)
{
	if(group)
	{
		return group->HasPendingTask();
	}
	else
	{
		return false;
	}
}

} // end namespace glf